package net.gdface.bean.openbeans;

import com.googlecode.openbeans.BeanInfo;
import com.googlecode.openbeans.IndexedPropertyDescriptor;
import com.googlecode.openbeans.IntrospectionException;
import com.googlecode.openbeans.Introspector;
import com.googlecode.openbeans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;

import net.gdface.annotations.ActiveOnClass;
import net.gdface.bean.BaseBeanPropertySupport;
import net.gdface.bean.NameResolver;
import net.gdface.bean.exception.BeanPropertyRuntimeException;
import net.gdface.bean.exception.CycleReferenceException;
import net.gdface.bean.exception.NestedNullException;
import net.gdface.bean.openbeans.descriptor.FieldPropertyDescriptor;
import net.gdface.bean.openbeans.descriptor.LenientDecoratorOfDescriptor;
import net.gdface.bean.openbeans.descriptor.MappedPropertyDescriptor;
import net.gdface.bean.openbeans.descriptor.NoStandardPropertyDescriptor;
import net.gdface.bean.openbeans.factory.PropertyDescriptorFactories;
import net.gdface.bean.openbeans.factory.PropertyDescriptorFactory;
import net.gdface.logger.SimpleLogger;
import net.gdface.reflection.MethodUtils;
import static net.gdface.json.JsonSupports.jsonSupportInstance;

/**
 * 参照 common-beanutils org.apache.commons.beanutils.PropertyUtilsBean 实现 Java Bean 的多级嵌套读写，
 * 扩展支持Map,类成员，JSON String(需要JSON库[fastjson or jackson]支持) ,并提供自定义 PropertyDescriptor 支持
 * @author guyadong
 * @since 2.7.0
 */
@ActiveOnClass(PropertyDescriptor.class)
public class BeanPropertySupportImpl extends BaseBeanPropertySupport {
	private static final Logger logger = SimpleLogger.getLogger(BeanPropertySupportImpl.class.getSimpleName());
	public static final BeanPropertySupportImpl BEAN_SUPPORT = new BeanPropertySupportImpl();
	private final LoadingCache<Class<?>, BeanIntrospectionData> descriptorsCache = CacheBuilder.newBuilder()
			.expireAfterAccess(1, TimeUnit.HOURS)
			.build(new CacheLoader<Class<?>, BeanIntrospectionData>(){

				@Override
				public BeanIntrospectionData load(Class<?> key) throws Exception {
					return getIntrospectionData0(key);
				}});
	private final Cache<String, Map<String, PropertyDescriptor>> mappedDescriptorsCache = CacheBuilder.newBuilder()
			.expireAfterAccess(1, TimeUnit.HOURS)
			.build();
	private final NameResolver resolver = new NameResolver();
	
	private final PropertyDescriptorFactories propertyDescriptorFactories = new PropertyDescriptorFactories();

    public BeanPropertySupportImpl() {
	}

	private BeanIntrospectionData  getIntrospectionData0(Class<?> beanClass) throws IntrospectionException {
		BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);

        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
        if (descriptors == null) {
            descriptors = new PropertyDescriptor[0];
        }
        LinkedList<PropertyDescriptor> descriptorList = new LinkedList<>(Arrays.asList(descriptors));
        LinkedList<Field> fields = new LinkedList<>(Arrays.asList(beanClass.getFields()));
        for(Iterator<Field> itor = fields.iterator();itor.hasNext();){
        	Field next = itor.next();
        	if(Modifier.isStatic(next.getModifiers())){
        		itor.remove();
        		continue;
        	}
        	for(PropertyDescriptor pd:descriptorList){
        		if(pd.getName().equals(next.getName())){
        			if(!pd.getPropertyType().equals(next.getType())){
        				throw new IntrospectionException("FIND Field with save name: "+ next.getName() +",but mismatch Type " + next.getType());          			
            		}
        			itor.remove();
        			break;
        		}
        	}
        }
        for(Field field:fields){
        	descriptorList.add(new FieldPropertyDescriptor(field));
        }
        return new BeanIntrospectionData(descriptorList.toArray(new PropertyDescriptor[descriptorList.size()]));
	}
	/**
	 * Obtains the {@code BeanIntrospectionData} object describing the specified bean
	 * class. This object is looked up in the internal cache. If necessary, introspection
	 * is performed now on the affected bean class, and the results object is created.
	 *
	 * @param beanClass the bean class in question
	 * @return the {@code BeanIntrospectionData} object for this class
	 * @throws IllegalArgumentException if the bean class is <b>null</b>
	 */
	private BeanIntrospectionData getIntrospectionData(Class<?> beanClass) {
	    if (beanClass == null) {
	        throw new IllegalArgumentException("No bean class specified");
	    }
		try {
			return descriptorsCache.get(beanClass);
		} catch (ExecutionException e) {
			throw new BeanPropertyRuntimeException(e.getCause());
		}	
	}

	public PropertyDescriptor[]  getPropertyDescriptors(Class<?> beanClass) {
		return getIntrospectionData(beanClass).getDescriptors();
	}
	/**
	 * 获取beanClass中所有具有指定读写类型(rw)的属性
	 * @param beanClass
	 * @param rw 属性类型标记 <br>
	 * 					<li>0 所有属性</li>
	 * 					<li>1 读属性</li>
	 * 					<li>2 写属性</li>
	 * 					<li>3 读写属性</li>
	 * @param lenient 是否为宽容模式---允许返回类型不为void的setter方法
	 * @return 属性名与PropertyDescriptor映射的Map对象
	 */
	private HashMap<String, PropertyDescriptor> getProperties0(Class<?> beanClass, int rw,boolean lenient) {
		HashMap<String, PropertyDescriptor> properties = new HashMap<String, PropertyDescriptor>();
		if (beanClass != null) {
			PropertyDescriptor[] origDescriptors = getPropertyDescriptors(beanClass);
			Boolean put;
			for (PropertyDescriptor pd : origDescriptors) {
				if(lenient && !(pd instanceof NoStandardPropertyDescriptor) && (2 == (rw&2))){
					pd = LenientDecoratorOfDescriptor.toDecorator(pd);
				}
				put = false;
				switch (rw &= 3) {
				case 1:
					put = hasReadMethod(pd);
					break;
				case 2:
					put = hasWriteMethod(pd);
					break;
				case 0:
				case 3:
					put = hasReadMethod(pd) && hasWriteMethod(pd);
					break;
				}
				if (put) {
					properties.put(pd.getName(), pd);
				}
			}
		}
		return properties;
	}
	/**
	 * 获取beanClass中所有具有指定读写类型(rw)的属性
	 * @param beanClass
	 * @param rw 属性类型标记 <br>
	 * 					<li>0 所有属性</li>
	 * 					<li>1 读属性</li>
	 * 					<li>2 写属性</li>
	 * 					<li>3 读写属性</li>
	 * @param lenient 是否为宽容模式---允许返回类型不为void的setter方法
	 * @return 属性名与PropertyDescriptor映射的Map对象
	 */
	public Map<String, PropertyDescriptor> getProperties(final Class<?> beanClass, final int rw,final boolean lenient) {
		if(null != beanClass){
			try {
				String key = String.format("%s_%d_%b", beanClass.getName(),rw,lenient); 
				return mappedDescriptorsCache.get(
						key, 
						new Callable<Map<String,PropertyDescriptor>>() {
							@Override
							public Map<String, PropertyDescriptor> call() throws Exception {
								return getProperties0(beanClass,rw,lenient);
							}
						});
			} catch (ExecutionException e) {
				throw new BeanPropertyRuntimeException(e.getCause());
			}
		}
		return Collections.emptyMap();
	}
	/**
	 * 获取beanClass中所有具有指定读写类型(rw)的属性
	 * @param beanClass
	 * @param rw 属性类型标记 <br>
	 * 					<li>0 所有属性</li>
	 * 					<li>1 读属性</li>
	 * 					<li>2 写属性</li>
	 * 					<li>3 读写属性</li>
	 * @return 属性名与PropertyDescriptor映射的Map对象
	 * @since 3.1.4
	 */
	public final Map<String, PropertyDescriptor> getProperties(Class<?> beanClass, int rw) {
		return getProperties(beanClass, rw, false);
	}
	/**
	 * <p>Return the mapped property descriptors for this bean class.</p>
	 * @see #getProperties(Class, int, boolean)
	 * @param beanClass
	 */
	public Map<String, PropertyDescriptor> getMappedPropertyDescriptors(final Class<?> beanClass) {
		return getProperties(beanClass, 3, true);
	}
    /**
	 * <p>Return the mapped property descriptors for this bean.</p>
	 *
	 * @param bean Bean to be introspected
	 * @return the mapped property descriptors
	 */
	public Map<String, PropertyDescriptor> getMappedPropertyDescriptors(Object bean) {
	
	    if (bean == null) {
	        return null;
	    }
	    return getMappedPropertyDescriptors(bean.getClass());
	
	}
	
	@Override
	public Set<String> getPropertyNames(Class<?> beanClass,int rw, boolean lenient){
		if(null != beanClass) {
			return Sets.newLinkedHashSet(getProperties(beanClass,rw,lenient).keySet());
		}
    	return Collections.emptySet();
	}
	@Override
    public void clearDescriptors() {
    	descriptorsCache.asMap().clear();
    	mappedDescriptorsCache.asMap().clear();
        Introspector.flushCaches();

    }
    /**
	 * <p>Retrieve the property descriptors for the specified bean,
	 * introspecting and caching them the first time a particular bean class
	 * is encountered.</p>
	 *
	 * @param bean Bean for which property descriptors are requested
	 * @return the property descriptors
	 *
	 * @exception IllegalArgumentException if <code>bean</code> is null
	 */
	public PropertyDescriptor[] getPropertyDescriptors(Object bean) {
	
	    if (bean == null) {
	        throw new IllegalArgumentException("No bean specified");
	    }
	    return (getPropertyDescriptors(bean.getClass()));
	
	}

	/**
     * <p>Retrieve the property descriptor for the specified property of the
     * specified bean, or return <code>null</code> if there is no such
     * descriptor.  This method resolves indexed and nested property
     * references in the same manner as other methods in this class, except
     * that if the last (or only) name element is indexed, the descriptor
     * for the last resolved property itself is returned.</p>
     *
     * @param bean Bean for which a property descriptor is requested
     * @param name Possibly indexed and/or nested name of the property for
     *  which a property descriptor is requested
     * @return the property descriptor
     *
     * @exception IllegalAccessException if the caller does not have
     *  access to the property accessor method
     * @exception IllegalArgumentException if <code>bean</code> or
     *  <code>name</code> is null
     * @exception IllegalArgumentException if a nested reference to a
     *  property returns null
     * @exception InvocationTargetException if the property accessor method
     *  throws an exception
     * @exception NoSuchMethodException if an accessor method for this
     *  propety cannot be found
     */
    public PropertyDescriptor getPropertyDescriptor(Object bean,
                                                           String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            String next = resolver.next(name);
            Object nestedBean = getPropertyChecked(bean, next);
            if (nestedBean == null) {
                throw new NestedNullException
                        ("Null property value for '" + next +
                        "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        // Remove any subscript from the final name value
        name = resolver.getProperty(name);

        // Look up and return this property from our cache
        // creating and adding it to the cache if not found.
        if (name == null) {
            return (null);
        }
        PropertyDescriptor result;
        try {
        	/** 优先获取非标准 Descriptor  */
        	result = propertyDescriptorFactories.nostandardDescriptorOf(bean,name);
        	if (result != null) {
        		return result;
        	}
		} catch (IntrospectionException e) {
		}
        /** 获取标准 Descriptor  */
        result = getIntrospectionData(bean.getClass()).getDescriptor(name);
        if (result != null) {
            return result;
        }
        Map<String, PropertyDescriptor> mappedDescriptors =
                getMappedPropertyDescriptors(bean);

        result = mappedDescriptors.get(name);
        if (result == null) {
            // not found, try to create it
            try {
            	/** 获取内置 Descriptor  */
            	result= propertyDescriptorFactories.builtinDescriptorOf(bean, name);
				if(null == result){
					result = new MappedPropertyDescriptor(name, bean.getClass());
				}
            } catch (IntrospectionException ie) {
                /* Swallow IntrospectionException
                 * TODO: Why?
                 */
            }
            if (result != null ) {
            	if(!(bean instanceof Map || bean instanceof Iterable)){
            		/** 动态对象不需要缓存 PropertyDescriptor 实例 */
            		mappedDescriptors.put(name, result);
            	}
            }
        }

        return result;

    }
    
    @Override
    protected Object getSimpleProperty(Object bean, String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }

        // Validate the syntax of the property name
        if (resolver.hasNested(name)) {
            throw new IllegalArgumentException
                    ("Nested property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isIndexed(name)) {
            throw new IllegalArgumentException
                    ("Indexed property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isMapped(name)) {
            throw new IllegalArgumentException
                    ("Mapped property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }else if (resolver.isSearched(name)) {
            throw new IllegalArgumentException
            ("Search property names are not allowed: Property '" +
            name + "' on bean class '" + bean.getClass() + "'");
}

        // Retrieve the property getter method for the specified property
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                    name + "' on class '" + bean.getClass() + "'" );
        }
        Method readMethod = getReadMethod(bean.getClass(), descriptor);
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name +
                    "' has no getter method in class '" + bean.getClass() + "'");
        }
       	return  invokeMethod(descriptor,readMethod, bean, EMPTY_OBJECT_ARRAY);

    }
    @Override
    protected Object getMappedProperty(Object bean,
    		String name, String key)
    				throws IllegalAccessException, InvocationTargetException,
    				NoSuchMethodException {

    	if (bean == null) {
    		throw new IllegalArgumentException("No bean specified");
    	}
    	if (name == null) {
    		throw new IllegalArgumentException("No name specified for bean class '" +
    				bean.getClass() + "'");
    	}
    	if (key == null) {
    		throw new IllegalArgumentException("No key specified for property '" +
    				name + "' on bean class " + bean.getClass() + "'");
    	}

    	Object result = null;

    	// Retrieve the property descriptor for the specified property
    	PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    	if (descriptor == null) {
    		throw new NoSuchMethodException("Unknown property '" +
    				name + "'+ on bean class '" + bean.getClass() + "'");
    	}

    	if (descriptor instanceof MappedPropertyDescriptor) {
    		// Call the keyed getter method if there is one
    		Method readMethod = ((MappedPropertyDescriptor) descriptor).
    				getMappedReadMethod();
    		readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
    		if (readMethod != null) {
    			Object[] keyArray = new Object[1];
    			keyArray[0] = key;
    			result = invokeMethod(descriptor,readMethod, bean, keyArray);
    		} else {
    			throw new NoSuchMethodException("Property '" + name +
    					"' has no mapped getter method on bean class '" +
    					bean.getClass() + "'");
    		}
    	} else {
    		/* means that the result has to be retrieved from a map */
    		Method readMethod = getReadMethod(bean.getClass(), descriptor);
    		if (readMethod != null) {
    			Object invokeResult = invokeMethod(descriptor,readMethod, bean, EMPTY_OBJECT_ARRAY);
    			/* test and fetch from the map */
    			if (invokeResult instanceof java.util.Map) {
    				result = ((java.util.Map<?, ?>)invokeResult).get(key);
    			}
    		} else {
    			throw new NoSuchMethodException("Property '" + name +
    					"' has no mapped getter method on bean class '" +
    					bean.getClass() + "'");
    		}
    	}
    	return result;

    }
    @SuppressWarnings("unchecked")
    @Override
    protected void setMappedProperty(Object bean, String name,
                                         String key, Object value)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }
        if (key == null) {
            throw new IllegalArgumentException("No key specified for property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }

        // Retrieve the property descriptor for the specified property
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }

        if (descriptor instanceof MappedPropertyDescriptor) {
            // Call the keyed setter method if there is one
            Method mappedWriteMethod =
                    ((MappedPropertyDescriptor) descriptor).
                    getMappedWriteMethod();
            mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
            if (mappedWriteMethod != null) {
                Object[] params = new Object[2];
                params[0] = key;
                params[1] = value;
                if (traceEnabled) {
                    String valueClassName =
                        value == null ? "<null>" : value.getClass().getName();
                    logger.info("setSimpleProperty: Invoking method "
                              + mappedWriteMethod + " with key=" + key
                              + ", value=" + value
                              + " (class " + valueClassName +")");
                }
                invokeMethod(descriptor,mappedWriteMethod, bean, params);
            } else {
                throw new NoSuchMethodException
                    ("Property '" + name + "' has no mapped setter method" +
                     "on bean class '" + bean.getClass() + "'");
            }
        } else {
          /* means that the result has to be retrieved from a map */
          Method readMethod = getReadMethod(bean.getClass(), descriptor);
          if (readMethod != null) {
            Object invokeResult = invokeMethod(descriptor,readMethod, bean, EMPTY_OBJECT_ARRAY);
            /* test and fetch from the map */
            if (invokeResult instanceof Map) {
            	((Map<String, Object>)invokeResult).put(key, value);
            }
          } else {
            throw new NoSuchMethodException("Property '" + name +
                    "' has no mapped getter method on bean class '" +
                    bean.getClass() + "'");
          }
        }

    }
    @Override
    protected Object getIndexedProperty(Object bean,
                                            String name, int index)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null || name.length() == 0) {
            return getIndexValue("", bean,index);
        }

        // Retrieve the property descriptor for the specified property
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }

        // Call the indexed getter method if there is one
        if (descriptor instanceof IndexedPropertyDescriptor) {
            Method readMethod = ((IndexedPropertyDescriptor) descriptor).
                    getIndexedReadMethod();
            readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
            if (readMethod != null) {
                Object[] subscript = new Object[1];
                /** index为 -1 时视为获取头部第一个元素 */
                subscript[0] = new Integer(-1 == index ? 0 : index);
                try {
                    return (invokeMethod(descriptor,readMethod,bean, subscript));
                } catch (InvocationTargetException e) {
                    if (e.getTargetException() instanceof
                            IndexOutOfBoundsException) {
                        throw (IndexOutOfBoundsException)
                                e.getTargetException();
                    } else {
                        throw e;
                    }
                }
            }
        }

        // Otherwise, the underlying property must be an array
        Method readMethod = getReadMethod(bean.getClass(), descriptor);
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name + "' has no " +
                    "getter method on bean class '" + bean.getClass() + "'");
        }

        // Call the property getter and return the value
        Object value = invokeMethod(descriptor,readMethod, bean, EMPTY_OBJECT_ARRAY);
        return getIndexValue(name,value,index);

    }
    @Override
	protected void setIndexedProperty(Object bean, String name,
                                          int index, Object value)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null || name.length() == 0) {
        	setOrAddIndexValue(name,bean,index, value);
        	return;
        }
        // Retrieve the property descriptor for the specified property
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }

        // Call the indexed setter method if there is one
        if (descriptor instanceof IndexedPropertyDescriptor) {
            Method writeMethod = ((IndexedPropertyDescriptor) descriptor).
                    getIndexedWriteMethod();
            writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
            if (writeMethod != null) {
                Object[] subscript = new Object[2];
                subscript[0] = new Integer(index);
                subscript[1] = value;
                try {
                    if (traceEnabled) {
                        String valueClassName =
                            value == null ? "<null>"
                                          : value.getClass().getName();
                        logger.info("setSimpleProperty: Invoking method "
                                  + writeMethod +" with index=" + index
                                  + ", value=" + value
                                  + " (class " + valueClassName+ ")");
                    }
                    invokeMethod(descriptor,writeMethod, bean, subscript);
                } catch (InvocationTargetException e) {
                	Throwables.throwIfInstanceOf(e.getTargetException(), IndexOutOfBoundsException.class);
                    throw e;
                }
                return;
            }
        }

        // Otherwise, the underlying property must be an array or a list
        Method readMethod = getReadMethod(bean.getClass(), descriptor);
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name +
                    "' has no getter method on bean class '" + bean.getClass() + "'");
        }

        // Call the property getter to get the array or list
        Object array = invokeMethod(descriptor,readMethod, bean, EMPTY_OBJECT_ARRAY);
        if(null ==array){
        	/** 如果字段为null,尝试创建新的List对象 */
        	Class<?> propertyType = descriptor.getPropertyType();
        	if(Object.class==propertyType || null == propertyType){
        		array = new LinkedList<>();
        		 // Otherwise, the underlying property must be an array or a list
                Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
                if (writeMethod == null) {
                    throw new NoSuchMethodException("Property '" + name +
                            "' has no setter method on bean class '" + bean.getClass() + "'");
                }
                invokeMethod(descriptor,writeMethod, bean, new Object[]{array});
        	}
        }
        setOrAddIndexValue(name,array,index, value);

    }
    @Override
    protected Object getSearchedProperty(Object bean,
    		String name,String field,Object conditionValue)
    				throws IllegalAccessException, InvocationTargetException,
    				NoSuchMethodException {
    	
    	if (bean == null) {
    		throw new IllegalArgumentException("No bean specified");
    	}
    	if (name == null || name.length() == 0) {
    		return searchIn(bean,field,conditionValue);
    	}
    	
    	// Retrieve the property descriptor for the specified property
    	PropertyDescriptor descriptor =
    			getPropertyDescriptor(bean, name);
    	if (descriptor == null) {
    		throw new NoSuchMethodException("Unknown property '" +
    				name + "' on bean class '" + bean.getClass() + "'");
    	}
    	
    	// Call the indexed getter method if there is one
    	if (descriptor instanceof IndexedPropertyDescriptor) {
    		throw new UnsupportedOperationException("UNSUPPORT IndexedPropertyDescriptor for search property");
    	}
    	
    	// Otherwise, the underlying property must be an array
    	Method readMethod = getReadMethod(bean.getClass(), descriptor);
    	if (readMethod == null) {
    		throw new NoSuchMethodException("Property '" + name + "' has no " +
    				"getter method on bean class '" + bean.getClass() + "'");
    	}
    	
    	// Call the property getter and return the value
    	Object value = invokeMethod(descriptor,readMethod, bean, EMPTY_OBJECT_ARRAY);
    	return searchIn(value,field,conditionValue);    	
    }
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    protected void setSimpleProperty(Object bean,
                                         String name, Object value)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }

        // Validate the syntax of the property name
        if (resolver.hasNested(name)) {
            throw new IllegalArgumentException
                    ("Nested property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isIndexed(name)) {
            throw new IllegalArgumentException
                    ("Indexed property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isMapped(name)) {
            throw new IllegalArgumentException
                    ("Mapped property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }else if (resolver.isSearched(name)) {
            throw new IllegalArgumentException
            ("Searched property names are not allowed: Property '" +
            name + "' on bean class '" + bean.getClass() + "'");
        }

        // Retrieve the property setter method for the specified property
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                    name + "' on class '" + bean.getClass() + "'" );
        }
        Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
        if (writeMethod == null) {
            throw new NoSuchMethodException("Property '" + name +
                    "' has no setter method in class '" + bean.getClass() + "'");
        }

        // Call the property setter method
        if (traceEnabled) {
            String valueClassName =
                value == null ? "<null>" : value.getClass().getName();
            logger.info("setSimpleProperty: Invoking method " + writeMethod
                      + " with value " + value + " (class " + valueClassName + ")");
        }
        {
        	/** 当value 与目标类型不同时的处理 */
        	Class<?> propertyType = descriptor.getPropertyType();
        	if(null == value){
        		// DO NOTHING
        	}else if(!propertyType.isInstance(value)){
        		if(descriptor instanceof NoStandardPropertyDescriptor 
        				&& ((NoStandardPropertyDescriptor)descriptor).isWritable(value)) {
        			/** 如果是非标准属性描述符且值为可写入类型则忽略 */
        			// DO NOTHING
        		}else if(String.class.equals(propertyType)){
        			Object old = getProperty0(bean, name);
        			Map<?,?> oldjson = new LinkedHashMap<>();
        			if(maybeJsonString(old) > 0){
        				Object parsed = jsonSupportInstance().parseOrEmptyMap((String) old,false);
        				if(parsed instanceof Map){
							oldjson = (Map<?,?>) parsed;
        				}
        			}
        			/** 合并value到原数据 */
        			copyProperties(oldjson, value);
        			/** value to json string */
        			value = jsonSupportInstance().toJSONString(oldjson);
        		}else if(value instanceof String){
					 value = jsonSupportInstance().parse((String)value, propertyType);
				}else if (Map.class.isInstance(value) && Map.class.isAssignableFrom(propertyType)) {
	        		Object old = getProperty0(bean, name);
	        		/** 合并value到原数据 */
	        		if(null != old){
	        			((Map) old).putAll((Map)value);
	        			value = old;
	        		}
				}else {
					// DO NOTHING(听天由命)
				}
        	}else if(String.class.equals(propertyType)){
        		Object old = getProperty0(bean, name);
        		if(maybeJsonString(old)>0 && maybeJsonString(value)>0){
        			Object oldparsed = jsonSupportInstance().parseOrEmptyMap((String) old);
        			Object valueparsed = jsonSupportInstance().parseOrEmptyMap((String) value);
        			if(oldparsed instanceof Map && valueparsed instanceof Map){
        				/** 合并value到原数据 */
        				((Map) oldparsed).putAll((Map)valueparsed);
        				value = jsonSupportInstance().toJSONString(oldparsed);
        			}
        		}
        	}else if (Map.class.isAssignableFrom(propertyType)) {
        		Object old = getProperty0(bean, name);
        		if(null != old){
        			/** 合并value到原数据 */
        			copyProperties(old, value);
        			value = old;
        		}
			}
        }
        invokeMethod(descriptor,writeMethod, bean, new Object[]{value});

    }
    /**
	 * <p>Return an accessible property getter method for this property,
	 * if there is one; otherwise return <code>null</code>.</p>
	 *
	 * @param clazz The class of the read method will be invoked on
	 * @param descriptor Property descriptor to return a getter for
	 * @return The read method
	 */
	private Method getReadMethod(Class<?> clazz, PropertyDescriptor descriptor) {
		if(descriptor instanceof NoStandardPropertyDescriptor){
			return ((NoStandardPropertyDescriptor)descriptor).getReadMethod(clazz, descriptor);
		}
	    return (MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod()));
	}

	/**
     * <p>Return an accessible property setter method for this property,
     * if there is one; otherwise return <code>null</code>.</p>
     *
     * @param clazz The class of the read method will be invoked on
     * @param descriptor Property descriptor to return a setter for
     * @return The write method
     */
	private Method getWriteMethod(Class<?> clazz, PropertyDescriptor descriptor) {
    	if(descriptor instanceof NoStandardPropertyDescriptor){
    		return ((NoStandardPropertyDescriptor)descriptor).getWriteMethod(clazz, descriptor);
    	}
        BeanIntrospectionData data = getIntrospectionData(clazz);
        return (MethodUtils.getAccessibleMethod(clazz,
                data.getWriteMethod(clazz, descriptor)));
    }
	@Override
	public void copyProperties(Object dest, Object orig, boolean ignoreNull, boolean ignoreEmpty) {
		
		if (dest == null) {
			throw new IllegalArgumentException
			("No destination bean specified");
		}
		if (orig == null) {
			throw new IllegalArgumentException("No origin bean specified");
		}
		
		if (orig instanceof Map) {
			Iterator<?> entries = ((Map<?, ?>) orig).entrySet().iterator();
			while (entries.hasNext()) {
				Map.Entry<?, ?> entry = (Entry<?, ?>) entries.next();
				String name = (String)entry.getKey();
				if (isWriteable(dest, name)) {
					try {
						Object value = entry.getValue();
						if(!ignoreNull || null != value){
							if(!ignoreEmpty || !isEmpty(value)){
								// 向 dest 写入字段字段值
								setSimpleProperty(dest, name, value);
							}
						}
					} catch (NoSuchMethodException e) {
						if (traceEnabled) {
							logger.log(Level.INFO, "Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
						}
					} catch (ReflectiveOperationException e) {
						throw new RuntimeException(e);
					}
				}
			}
		} else /* if (orig is a standard JavaBean) */ {
			PropertyDescriptor[] origDescriptors =
					getPropertyDescriptors(orig);
			for (int i = 0; i < origDescriptors.length; i++) {
				String name = origDescriptors[i].getName();
				if (isReadable(orig, name) && isWriteable(dest, name)) {
					try {
						Object value = getSimpleProperty(orig, name);
						if(!ignoreNull || null != value){
							if(!ignoreEmpty || !isEmpty(value)){
								// 向 dest 写入字段字段值
								setSimpleProperty(dest, name, value);
							}
						}
					} catch (NoSuchMethodException e) {
						if (traceEnabled) {
							logger.log(Level.INFO, "Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
						}
					} catch (ReflectiveOperationException e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
		
	}

	@Override
    public void copyProperties(Object dest, Object orig) {
        copyProperties(dest,orig,false,false);
    }
	@Override
	public boolean isReadable(Object bean, String name) {
	
	    // Validate method parameters
	    if (bean == null) {
	        throw new IllegalArgumentException("No bean specified");
	    }
	    if (name == null) {
	        throw new IllegalArgumentException("No name specified for bean class '" +
	                bean.getClass() + "'");
	    }
	
	    // Resolve nested references
	    while (resolver.hasNested(name)) {
	        String next = resolver.next(name);
	        Object nestedBean = null;
	        try {
	            nestedBean = getPropertyChecked(bean, next);
	        } catch (IllegalAccessException e) {
	            return false;
	        } catch (InvocationTargetException e) {
	            return false;
	        } catch (NoSuchMethodException e) {
	            return false;
	        }
	        if (nestedBean == null) {
	            throw new NestedNullException
	                    ("Null property value for '" + next +
	                    "' on bean class '" + bean.getClass() + "'");
	        }
	        bean = nestedBean;
	        name = resolver.remove(name);
	    }
	
	    // Remove any subscript from the final name value
	    name = resolver.getProperty(name);
	
	    try {
	        PropertyDescriptor desc =
	            getPropertyDescriptor(bean, name);
	        if (desc != null) {
	            Method readMethod = getReadMethod(bean.getClass(), desc);
	            if (readMethod == null) {
	                if (desc instanceof IndexedPropertyDescriptor) {
	                    readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
	                } else if (desc instanceof MappedPropertyDescriptor) {
	                    readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
	                }
	                readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
	            }
	            return (readMethod != null);
	        } else {
	            return (false);
	        }
	    } catch (IllegalAccessException e) {
	        return (false);
	    } catch (InvocationTargetException e) {
	        return (false);
	    } catch (NoSuchMethodException e) {
	        return (false);
	    }
	
	
	}

	@Override
	public boolean isWriteable(Object bean, String name) {
	
	    // Validate method parameters
	    if (bean == null) {
	        throw new IllegalArgumentException("No bean specified");
	    }
	    if (name == null) {
	        throw new IllegalArgumentException("No name specified for bean class '" +
	                bean.getClass() + "'");
	    }
	
	    // Resolve nested references
	    while (resolver.hasNested(name)) {
	        String next = resolver.next(name);
	        Object nestedBean = null;
	        try {
	            nestedBean = getPropertyChecked(bean, next);
	        } catch (IllegalAccessException e) {
	            return false;
	        } catch (InvocationTargetException e) {
	            return false;
	        } catch (NoSuchMethodException e) {
	            return false;
	        }
	        if (nestedBean == null) {
	            throw new NestedNullException
	                    ("Null property value for '" + next +
	                    "' on bean class '" + bean.getClass() + "'");
	        }
	        bean = nestedBean;
	        name = resolver.remove(name);
	    }
	
	    // Remove any subscript from the final name value
	    name = resolver.getProperty(name);
	
	
	    try {
	        PropertyDescriptor desc =
	            getPropertyDescriptor(bean, name);
	        if (desc != null) {
	            Method writeMethod = getWriteMethod(bean.getClass(), desc);
	            if (writeMethod == null) {
	                if (desc instanceof IndexedPropertyDescriptor) {
	                    writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
	                } else if (desc instanceof MappedPropertyDescriptor) {
	                    writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
	                }
	                writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
	            }
	            return (writeMethod != null);
	        } else {
	            return (false);
	        }
	    } catch (IllegalAccessException e) {
	        return (false);
	    } catch (InvocationTargetException e) {
	        return (false);
	    } catch (NoSuchMethodException e) {
	        return (false);
	    }
	}

	@Override
    public boolean hasCycleReference(Object bean) {
    	try {
    		checkCycleReference(bean);
			return false;
		} catch (CycleReferenceException e) {
			return true;
		}
    }
    @Override
    protected Object tryConstructIfNull(Object nestedBean,Object bean, String name, Class<?> expectType) 
    		throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
    	if(null != nestedBean){
    		return nestedBean;
    	}
    	if(null == bean){
    		throw new NullPointerException("bean is null");
    	}
    	if(isEmpty(name)) {
    		throw new NullPointerException("name is null or empty");
    	}
    	PropertyDescriptor descriptor;
    	try {
    		descriptor = getPropertyDescriptor(bean,name);
    		if(null == descriptor){
                throw new NoSuchMethodException("Unknown property '" +
                        name + "' on class '" + bean.getClass() + "'" );
            }
		} catch (ReflectiveOperationException e) {
			throw new BeanPropertyRuntimeException(e);
		}
		try {
			if(null == expectType){
				expectType = descriptor.getPropertyType();
				if(List.class.equals(expectType)){
					expectType = ArrayList.class;
				}else if(Set.class.equals(expectType)){
					expectType = LinkedHashSet.class;
				}else if(Map.class.equals(expectType)){
					expectType = LinkedHashMap.class;
				}
			}
			Object newBean;
			if(String.class.equals(expectType)){
				/** String 类型时设置为空的JSON 字符串 */
				newBean = "{}";
			}else {
				newBean = expectType.getConstructor().newInstance();
			}
			setProperty0(bean, name, newBean);
			return newBean;
    	} catch (InstantiationException | IllegalArgumentException | SecurityException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    		throw new NestedNullException
    		("Null property value for '" + name +
    				"' on bean class '" + bean.getClass() + "',AND FAIL to try construct new instance",e);
    	}
    }
	/** This just catches and wraps IllegalArgumentException. */
	private Object invokeMethod(
	                    Method method,
	                    Object bean,
	                    Object[] values)
	                        throws
	                            IllegalAccessException,
	                            InvocationTargetException {
	    if(bean == null && !NoStandardPropertyDescriptor.class.isAssignableFrom(method.getDeclaringClass())) {
	        throw new IllegalArgumentException("No bean specified " +
	            "- this should have been checked before reaching this method");
	    }
	
	    try {
	
	        return method.invoke(bean, values);
	
	    } catch (NullPointerException cause) {
	        // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
	        // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
	        String valueString = "";
	        if (values != null) {
	            for (int i = 0; i < values.length; i++) {
	                if (i>0) {
	                    valueString += ", " ;
	                }
	                if (values[i] == null) {
	                    valueString += "<null>";
	                } else {
	                    valueString += (values[i]).getClass().getName();
	                }
	            }
	        }
	        String expectedString = "";
	        Class<?>[] parTypes = method.getParameterTypes();
	        if (parTypes != null) {
	            for (int i = 0; i < parTypes.length; i++) {
	                if (i > 0) {
	                    expectedString += ", ";
	                }
	                expectedString += parTypes[i].getName();
	            }
	        }
	        IllegalArgumentException e = new IllegalArgumentException(
	            "Cannot invoke " + method.getDeclaringClass().getName() + "."
	            + method.getName() + " on bean class '" + bean.getClass() +
	            "' - " + cause.getMessage()
	            // as per https://issues.apache.org/jira/browse/BEANUTILS-224
	            + " - had objects of type \"" + valueString
	            + "\" but expected signature \""
	            +   expectedString + "\"",
	            cause
	            );
	        
	        throw e;
	    } catch (IllegalArgumentException cause) {
	        String valueString = "";
	        if (values != null) {
	            for (int i = 0; i < values.length; i++) {
	                if (i>0) {
	                    valueString += ", " ;
	                }
	                if (values[i] == null) {
	                    valueString += "<null>";
	                } else {
	                    valueString += (values[i]).getClass().getName();
	                }
	            }
	        }
	        String expectedString = "";
	        Class<?>[] parTypes = method.getParameterTypes();
	        if (parTypes != null) {
	            for (int i = 0; i < parTypes.length; i++) {
	                if (i > 0) {
	                    expectedString += ", ";
	                }
	                expectedString += parTypes[i].getName();
	            }
	        }
	        IllegalArgumentException e = new IllegalArgumentException(
	            "Cannot invoke " + method.getDeclaringClass().getName() + "."
	            + method.getName() + " on bean class '" + bean.getClass() +
	            "' - " + cause.getMessage()
	            // as per https://issues.apache.org/jira/browse/BEANUTILS-224
	            + " - had objects of type \"" + valueString
	            + "\" but expected signature \""
	            +   expectedString + "\"",
	            cause
	            );
	        throw e;
	
	    }
	}
    /** This just catches and wraps IllegalArgumentException. */
	private Object invokeMethod(
						PropertyDescriptor descriptor,
	                    Method method,
	                    Object bean,
	                    Object[] values)
	                        throws
	                            IllegalAccessException,
	                            InvocationTargetException {
		if(descriptor instanceof NoStandardPropertyDescriptor){
			InvokeMewthodContext context = new InvokeMewthodContext(descriptor, method, bean, values);
			((NoStandardPropertyDescriptor)descriptor).beforeInvokeMethod(context);
			return  invokeMethod(method, context.bean, context.values);
		}else{
        	// Call the property getter and return the value
        	return invokeMethod(method, bean, values);
		}
	}
	private static boolean hasReadMethod(PropertyDescriptor propertyDescriptor) {
		return null != propertyDescriptor &&  null != propertyDescriptor.getReadMethod();
	}
	private static boolean hasWriteMethod(PropertyDescriptor propertyDescriptor) {
		return null != propertyDescriptor &&  null != propertyDescriptor.getWriteMethod();
	}
    public void addPropertyDescriptorFactoryAsFirst(PropertyDescriptorFactory element) {
		propertyDescriptorFactories.addFirst(element);
	}
	public void addPropertyDescriptorFactory(PropertyDescriptorFactory element) {
		propertyDescriptorFactories.add(element);
	}
}
